package com.dilean.common.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.jdbc.core.PreparedStatementCreator;

import com.dilean.common.api.Properties;

public class DbDileanPrioperties implements Properties, InitializingBean
{
	public static final String PROPERTIES_TABLE = "DILEAN_PROPERTIES";
	
	private final Map<String, String> cache = new ConcurrentHashMap<String, String>();
	private final Lock resetLock = new ReentrantLock();
	private static final Log log = LogFactory.getLog(DbDileanPrioperties.class);
	
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	@Override
	public void reset() 
	{
		if (!resetLock.tryLock())
		{
			if (log.isDebugEnabled())
				log.debug("Try to reset but resetLock is not available");
			
			return;
		}
		
		try
		{
			jdbcTemplate.execute("select KEY, VALUE from " + PROPERTIES_TABLE, new PreparedStatementCallback<Object>() 
			{
				@Override
				public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException 
				{
				    ResultSet rs = null;
					
					try
					{
						rs = ps.executeQuery();
						
						while (rs.next())
							cache.put(rs.getString("KEY"), rs.getString("VALUE"));
						
						if (log.isDebugEnabled())
							log.debug("Reseted properties are : " + cache);
							
					}
					finally
					{
						if (rs != null)
							rs.close();
					}
					
					return null;
				}
				
			});
			
		}
		finally
		{
			resetLock.unlock();
		}
	}

	@Override
	public String get(String key) 
	{
		return cache.get(key);
	}

	@Override
	public void afterPropertiesSet() throws Exception 
	{
		reset();
	}

	@Override
	public boolean put(final String key, final String value) 
	{
		if (key == null || "".equals(key) || value == null || "".equals(value))
		{
			log.info("Cannot set property [key=" + key + "][value=" + value + "]");
			return false;
		}
		
		final String sql = 
			"MERGE INTO " + PROPERTIES_TABLE + " t\n" +
		    "USING (SELECT ? KEY, ? VALUE FROM DUAL) s\n" + 
		    "ON (t.KEY = s.KEY)\n" + 
		    "WHEN MATCHED THEN\n" + 
		    "	UPDATE SET t.VALUE = s.VALUE\n" + 
		    "WHEN NOT MATCHED THEN\n" + 
		    "INSERT (t.KEY, t.VALUE)\n" + 
		    "	VALUES (s.KEY, s.VALUE)\n";
		
		return jdbcTemplate.execute(
				new PreparedStatementCreator() 
				{
			
					@Override
					public PreparedStatement createPreparedStatement(Connection con) throws SQLException 
					{
						final PreparedStatement ps = con.prepareStatement(sql);
						ps.setString(1, key.toUpperCase());
						ps.setString(2, value);
						return ps;
					}
				},
		
			    new PreparedStatementCallback<Boolean>()
				{
					@Override
					public Boolean doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException 
					{
						ps.execute();
						cache.put(key.toUpperCase(), value);
						return true;
					}
				});
	}

	@Override
	public boolean remove(final String key) 
	{
		if (key == null || "".equals(key))
		{
			log.info("Cannot set remove property [key=" + key + "]");
			return false;
		}

		final String sql = "delete from " + PROPERTIES_TABLE + " where KEY = ?";
		
		return jdbcTemplate.execute(
				new PreparedStatementCreator() 
				{
			
					@Override
					public PreparedStatement createPreparedStatement(Connection con) throws SQLException 
					{
						final PreparedStatement ps = con.prepareStatement(sql);
						ps.setString(1, key.toUpperCase());
						return ps;
					}
				},
		
			    new PreparedStatementCallback<Boolean>()
				{
					@Override
					public Boolean doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException 
					{
						ps.execute();
						cache.remove(key.toUpperCase());
						return true;
					}
				});
	}

}
